package com.rslai.base.tool.servertest.casereader;

import com.alibaba.fastjson.JSONObject;
import com.rslai.base.tool.servertest.command.CallStepCommand;
import com.rslai.base.tool.servertest.command.CommandFactory;
import com.rslai.base.tool.servertest.command.StepCommand;
import com.rslai.base.tool.servertest.command.TearDownStepCommand;
import com.rslai.base.tool.servertest.exception.DuplicateIdException;
import com.rslai.base.tool.servertest.model.DataDrivenTestCase;
import com.rslai.base.tool.servertest.model.KeyValueStore;
import com.rslai.base.tool.servertest.model.TestCase;
import com.rslai.base.tool.servertest.model.TestSuite;
import com.rslai.base.tool.servertest.preprocessor.TestCasePreProcessor;
import com.rslai.base.tool.servertest.util.CloneUtil;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Dom4jCaseReader implements TestCaseReader {
    private static final Logger logger = LoggerFactory.getLogger(Dom4jCaseReader.class);

    protected static final ThreadLocal<String> threadLocal = new ThreadLocal();
    protected static final Map<String, List<Object>> SUITE_ID_CACHE = new HashMap();
    protected static final Map<String, List<Object>> CASE_ID_CACHE = new HashMap();

    public TestSuite readTestCase(String fileName) throws FileNotFoundException {
        threadLocal.set(fileName);
        TestCasePreProcessor preProcessor = new TestCasePreProcessor();
        Document document = preProcessor.prepare(fileName);
        if (document == null) {
            logger.info(new StringBuilder().append(fileName).append("不是testcase文件").toString());
            return null;
        }
        TestSuite testSuite = getTestCase(document);
        setCaseFileName(testSuite, fileName);
        return testSuite;
    }

    private void setCaseFileName(TestSuite testSuite, String fileName) {
        String searchStr = "test-classes";
        int index = StringUtils.indexOf(fileName, searchStr);
        if (index != -1) {
            fileName = StringUtils.substring(fileName, index + searchStr.length());
            testSuite.setCaseFileName(fileName);
        } else {
            testSuite.setCaseFileName(fileName);
        }
    }

    private TestSuite getTestCase(Document document) {
        TestSuite testSuite = new TestSuite();
        Element rootElement = document.getRootElement();
        Map attributeMap = getAttributeMap(rootElement);
        String suiteId = getId((String) attributeMap.get("id"));
        checkDuplicateId(suiteId, SUITE_ID_CACHE);
        testSuite.setId(suiteId);
        String tags = (String) attributeMap.get("tag");
        testSuite.setTag(getTagList(tags));
        testSuite.setDesc((String) attributeMap.get("desc"));
        List backGrounds = getBackGrounds(rootElement, testSuite);

        List testCases = getCases(rootElement, testSuite);
        testSuite.setBeforeSuite(getBeforeSuite(rootElement, testSuite));
        testSuite.setAfterSuite(getAfterSuite(rootElement, testSuite));
        testSuite.setBeforeCase(getBeforeCase(rootElement, testSuite));
        testSuite.setAfterCase(getAfterCase(rootElement, testSuite));
        testSuite.setBackGrounds(backGrounds);
        testSuite.setTestCases(testCases);
        return testSuite;
    }

    private String getId(String id) {
        return StringUtils.replace(StringUtils.trim(id), " ", "_");
    }

    private String getSuiteDesc(TestSuite testSuite) {
        return StringUtils.isBlank(testSuite.getDesc()) ? testSuite.getId() : testSuite.getDesc();
    }

    private String getSuiteId(TestSuite testSuite) {
        return StringUtils.isBlank(testSuite.getId()) ? testSuite.getDesc() : testSuite.getId();
    }

    private List<KeyValueStore> getAttribute(Element element) {
        List attributes = new ArrayList();
        Iterator iterator = element.attributeIterator();
        while (iterator.hasNext()) {
            Attribute attribute = (Attribute) iterator.next();
            String attributeName = attribute.getName();
            String attributeValue = attribute.getValue();
            attributes.add(new KeyValueStore(attributeName, attributeValue));
        }
        return attributes;
    }

    private Map<String, String> getAttributeMap(Element element) {
        List attribute = getAttribute(element);
        return convertListKeyValueToMap(attribute);
    }

    private Map<String, String> convertListKeyValueToMap(List<KeyValueStore> list) {
        Map map = new HashMap();
        for (KeyValueStore kvs : list) {
            map.put(kvs.getName(), (String) kvs.getValue());
        }
        return map;
    }

    private List<TestCase> getCases(Element document, TestSuite testSuite) {
        List testCases = new ArrayList();
        testCases.addAll(getCase(document, "case", testSuite));
        testCases.addAll(getCase(document, "data-case", testSuite));
        checkDuplicateId(testCases, CASE_ID_CACHE);
        return testCases;
    }

    private void checkDuplicateId(List<TestCase> cases, Map<String, List<Object>> cache) {
        for (TestCase testCase : cases)
            checkDuplicateId(testCase.getId(), cache);
    }

    private void checkDuplicateId(String id, Map<String, List<Object>> cache) {
        for (Map.Entry entry : cache.entrySet()) {
            String file = (String) entry.getKey();
            List ids = (List) entry.getValue();
            if ((ids != null) && (ids.contains(id))) {
                String message;
                if (file.equals(threadLocal.get()))
                    message = new StringBuilder().append("文件<").append(file).append(">里的有重复的ID[").append(id).append("]").toString();
                else {
                    message = new StringBuilder().append("文件<").append(file).append(">与文件<").append((String) threadLocal.get()).append(">里的ID[").append(id).append("] 重复").toString();
                }
                throw new DuplicateIdException(message);
            }
        }
        List list = (List) cache.get(threadLocal.get());
        if (list == null) {
            list = new ArrayList();
            cache.put(threadLocal.get(), list);
        }
        list.add(id);
    }

    private CallStepCommand getAutoParamCheckCallCommand(List<StepCommand> commands) {
        if (commands == null) return null;
        for (StepCommand sc : commands) {
            if ((sc instanceof CallStepCommand)) {
                return (CallStepCommand) sc;
            }
        }
        return null;
    }

    private List<TestCase> getBeforeSuite(Element element, TestSuite testSuite) {
        List beforeSuit = getCase(element, "beforeSuit", testSuite);
        TestCase tc;
        for (Iterator i$ = beforeSuit.iterator(); i$.hasNext(); tc.setDesc((new StringBuilder()).append(getSuiteDesc(testSuite)).append("->BeforeSuit").toString()))
            tc = (TestCase) i$.next();

        return beforeSuit;
    }

    private List<TestCase> getAfterSuite(Element element, TestSuite testSuite) {
        List afterSuit = getCase(element, "afterSuit", testSuite);
        TestCase tc;
        for (Iterator i$ = afterSuit.iterator(); i$.hasNext(); tc.setDesc((new StringBuilder()).append(getSuiteDesc(testSuite)).append("->AfterSuit").toString()))
            tc = (TestCase) i$.next();

        return afterSuit;
    }

    private List<TestCase> getBeforeCase(Element element, TestSuite testSuite) {
        return getCase(element, "beforeCase", testSuite);
    }

    private List<TestCase> getAfterCase(Element element, TestSuite testSuite) {
        return getCase(element, "afterCase", testSuite);
    }

    private List<TestCase> getBackGrounds(Element document, TestSuite testSuite) {
        Element backgrounds = document.element("backgrounds");
        return getCase(backgrounds, "background", testSuite);
    }

    private List<TestCase> getCase(Element document, String elementName, TestSuite testSuite) {
        List testCases = new ArrayList();
        if (document == null) {
            return testCases;
        }
        List caseElements = document.elements(elementName);
        Iterator i$;
        if (caseElements != null) {
            for (i$ = caseElements.iterator(); i$.hasNext(); ) {
                Object caseElement = i$.next();
                Element next = (Element) caseElement;
                testCases.addAll(createTestCase(next, testSuite));
            }
        }
        return testCases;
    }

    private List<TestCase> createTestCase(Element next, TestSuite testSuite) {
        TestCase testCase = newTestCase(next, testSuite);
        if (isAutoParamTestCase(next)) {
            checkParamCheckElementCount(next.elements("paramcheck"));
            return createAutoParamCheckTestCase(testCase, next, next.element("paramcheck"));
        }
        List commands = CommandFactory.getInstance().getCommands(next);
        testCase.setTearDownCommand(getTearDownCommand(commands));
        testCase.setBodyCommand(getBodyCommand(filterBodyCommand(commands)));
        return Arrays.asList(new TestCase[]{testCase});
    }

    private boolean isAutoParamTestCase(Element next) {
        List paramCheckElements = next.elements("paramcheck");
        return (paramCheckElements != null) && (paramCheckElements.size() > 0);
    }

    private List<TestCase> createAutoParamCheckTestCase(TestCase testCase, Element parentElement, Element paramElement) {
        List result = new ArrayList();
        int index = getParamCheckElementIndex(parentElement, paramElement);
        List checkCommands = getAutoParamCheckCommands(paramElement);
        removeAutoCheckElement(parentElement, paramElement);
        List commands = CommandFactory.getInstance().getCommands(parentElement);
        List excludes = getAutoParamCommandAttributeValue(paramElement, "exclude");
        List includes = getAutoParamCommandAttributeValue(paramElement, "include");
        List values = getAutoParamCommandAttributeValue(paramElement, "value");
        if (checkCommands != null && checkCommands.size() > 0) {
            CallStepCommand callCommand = getAutoParamCheckCallCommand(checkCommands);
            int commandIndex = checkCommands.indexOf(callCommand);
            Set needReplaceNames = new HashSet();
            collectionParameters(callCommand.getParams(), needReplaceNames);
            Integer count = Integer.valueOf(0);
            for (Iterator i$ = values.iterator(); i$.hasNext(); ) {
                String value = (String) i$.next();
                Iterator j$ = needReplaceNames.iterator();
                while (j$.hasNext()) {
                    String needReplaceName = (String) j$.next();
                    if (!notNeedReplace(needReplaceName, excludes, includes)) {
                        CallStepCommand newCallStepCommand = (CallStepCommand) callCommand.cloneCommand();
                        replaceParameter(newCallStepCommand.getParams(), value, needReplaceName);
                        String suffix = (new StringBuilder()).append(needReplaceName).append("=").append(value).toString();
                        TestCase tc = new TestCase(concat(new String[]{
                                testCase.getId(), suffix, count.toString()
                        }), testCase.getTag(), concat(new String[]{
                                testCase.getDesc(), suffix
                        }));
                        List newCommands = CloneUtil.cloneStepCommand(commands);
                        List stepCommands = CloneUtil.cloneStepCommand(checkCommands);
                        stepCommands.set(commandIndex, newCallStepCommand);
                        newCommands.addAll(index, stepCommands);
                        tc.setTearDownCommand(getTearDownCommand(newCommands));
                        StepCommand bodyCommand = getBodyCommand(filterBodyCommand(newCommands));
                        tc.setBodyCommand(bodyCommand);
                        result.add(tc);
                        count = Integer.valueOf(count.intValue() + 1);
                    }
                }
            }

        }
        return result;
    }

    private boolean notNeedReplace(String name, List<String> excludes, List<String> includes) {
        if (excludes.contains(name)) {
            return true;
        }

        if ((includes.size() != 0) && (!includes.contains(name))) {
            return true;
        }
        return false;
    }

    private void collectionParameters(List<KeyValueStore> parameters, Set<String> parameterNames) {
        for (KeyValueStore parameter : parameters) {
            Object value = parameter.getValue();
            if (isJson(value)) {
                parameterNames.add(parameter.getName());
                collectionJsonParameter(parameter.getName(), (String) value, parameterNames);
            } else if ((value instanceof String)) {
                parameterNames.add(parameter.getName());
            } else if ((value instanceof Map)) {
                collectParameterInternal((Map) value, parameterNames);
            }
        }
    }

    private void collectionJsonParameter(String key, String json, Set<String> parameterNames) {
        Object object = JSONObject.parse(json);
        Iterator iterator = ((Map) object).entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            if ((entry.getKey() instanceof String))
                parameterNames.add(new StringBuilder().append(key).append(".").append(entry.getKey().toString().trim()).toString());
        }
    }

    private void collectParameterInternal(Map parameters, Set<String> parameterNames) {
        for (Iterator i$ = parameters.keySet().iterator(); i$.hasNext(); ) {
            Object o = i$.next();
            Object value = parameters.get(o);
            if ((value instanceof String))
                parameterNames.add(o.toString());
            else if ((value instanceof Map))
                collectParameterInternal((Map) value, parameterNames);
        }
    }

    private void replaceParameter(List<KeyValueStore> parameters, String value, String replaceName) {
        for (KeyValueStore parameter : parameters) {
            Object parameterValue = parameter.getValue();
            if (isJson(parameterValue)) {
                if (replaceName.equals(parameter.getName())) {
                    parameter.setValue(value);
                    return;
                }
                String json = replaceJsonParameter(parameter.getName(), (String) parameterValue, value, replaceName);
                parameter.setValue(json);
            } else if ((parameterValue instanceof Map)) {
                replaceParameterInternal((Map) parameterValue, value, replaceName);
            } else if ((parameterValue instanceof String)) {
                String key = parameter.getName();

                if (key.equals(replaceName)) {
                    parameter.setValue(value);
                }
            }
        }
    }

    private String replaceJsonParameter(String key, String json, String value, String replaceName) {
        Map map = (Map) JSONObject.parse(json);
        Iterator iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            if (((entry.getKey() instanceof String)) &&
                    (replaceName.equals(new StringBuilder().append(key).append(".").append(entry.getKey().toString().trim()).toString()))) {
                map.put(entry.getKey(), value);
            }
        }

        return JSONObject.toJSONString(map);
    }

    private void replaceParameterInternal(Map parameters, String value, String replaceName) {
        for (Iterator i$ = parameters.keySet().iterator(); i$.hasNext(); ) {
            Object o = i$.next();
            Object parameterValue = parameters.get(o);

            if ((parameterValue instanceof Map)) {
                replaceParameterInternal((Map) parameterValue, value, replaceName);
            } else if ((parameterValue instanceof String)) {
                String key = o.toString();

                if (key.equals(replaceName)) {
                    parameters.put(key, value);
                }
            }
        }
    }

    private TestCase newTestCase(Element element, TestSuite testSuite) {
        Map attributeMap = getAttributeMap(element);
        TestCase testCase;
        if (element.getName().equals("case"))
            testCase = new TestCase();
        else {
            testCase = new DataDrivenTestCase();
        }
        String id = getId((String) attributeMap.get("id"));
        String desc = (String) attributeMap.get("desc");

        testCase.setId(new StringBuilder().append("[").append(getSuiteId(testSuite)).append("]").append(id == null ? desc : id).toString());
        testCase.setDesc(desc == null ? id : desc);
        String tags = (String) attributeMap.get("tag");
        testCase.setTag(getTagList(tags));
        return testCase;
    }

    private String concat(String[] items) {
        return StringUtils.join(items, "-");
    }

    private List<String> getAutoParamCommandAttributeValue(Element element, String attributeName) {
        Attribute exclude = element.attribute(attributeName);
        if (exclude == null) {
            return Collections.emptyList();
        }
        String value = exclude.getValue();
        return value == null ? new ArrayList() : Arrays.asList(value.split(","));
    }

    private List<StepCommand> getAutoParamCheckCommands(Element paramElement) {
        return CommandFactory.getInstance().getCommands(paramElement);
    }

    private int getParamCheckElementIndex(Element parent, Element element) {
        Iterator iterator = parent.elementIterator();
        int index = 0;
        while (iterator.hasNext()) {
            Element e = (Element) iterator.next();
            if (e.getName().equals(element.getName())) break;
            index++;
        }

        return index;
    }

    private void checkParamCheckElementCount(List paramCheckElements) {
        if ((paramCheckElements != null) && (paramCheckElements.size() > 1))
            throw new RuntimeException(new StringBuilder().append("param check command only allowed 1 times, but find ").append(paramCheckElements.size()).append(" times").toString());
    }

    private void removeAutoCheckElement(Element element, Element removeElement) {
        element.remove(removeElement);
    }

    private List<String> getTagList(String tags) {
        if (tags == null) {
            return null;
        }
        return Arrays.asList(tags.split(","));
    }

    private StepCommand getBodyCommand(List<StepCommand> commands) {
        if (commands.size() > 1) {
            for (int index = 0; index < commands.size() - 1; index++) {
                ((StepCommand) commands.get(index)).setNextCommand((StepCommand) commands.get(index + 1));
            }
        }
        return commands.size() == 0 ? null : (StepCommand) commands.get(0);
    }

    private List<StepCommand> filterBodyCommand(List<StepCommand> commands) {
        List result = new ArrayList(commands.size());
        for (StepCommand command : commands) {
            if (isBodyCommand(command)) {
                result.add(command);
            }
        }
        return result;
    }

    private boolean isBodyCommand(StepCommand command) {
        return (command == null) || (!(command instanceof TearDownStepCommand));
    }

    private StepCommand getTearDownCommand(List<StepCommand> commands) {
        for (StepCommand command : commands) {
            if ((command instanceof TearDownStepCommand)) {
                return command;
            }
        }
        return null;
    }

    private boolean isJson(Object value) {
        if (!(value instanceof String)) return false;
        String json = value.toString();
        return (json.startsWith("{")) && (json.endsWith("}"));
    }
}
